-
Notifications
You must be signed in to change notification settings - Fork 10
Support tls certificates reload and crls #66
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
2bc7594
to
a7a17be
Compare
@behos Would you mind take a look ? Does it solve your concerns ? I included a file-based certs in test. zookeeper-client-rust/src/tls/client.rs Lines 352 to 452 in a7a17be
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, the file based implementation can either be exposed here or delegated to external services.
src/tls/client.rs
Outdated
struct FileBasedDynamicCerts { | ||
ca: Ca, | ||
dir: TempDir, | ||
certs: TlsDynamicCerts, | ||
feedback: UnboundedReceiver<()>, | ||
_task: TaskHandle<()>, | ||
} | ||
|
||
struct EventSender { | ||
sender: UnboundedSender<Event>, | ||
} | ||
|
||
impl notify::EventHandler for EventSender { | ||
fn handle_event(&mut self, event: Result<Event, notify::Error>) { | ||
if let Ok(event) = event { | ||
self.sender.unbounded_send(event).unwrap(); | ||
} | ||
} | ||
} | ||
|
||
impl FileBasedDynamicCerts { | ||
pub fn new(ca: Ca, client_name: &str) -> Self { | ||
let dir = TempDir::new().unwrap(); | ||
Self::generate_cert(&ca, dir.path(), client_name); | ||
let (certs, feedback, _task) = Self::load_dynamic_certs(dir.path()); | ||
Self { ca, dir, certs, feedback, _task } | ||
} | ||
|
||
fn load_dynamic_certs(dir: &Path) -> (TlsDynamicCerts, UnboundedReceiver<()>, TaskHandle<()>) { | ||
let cert_path = dir.join("cert.pem").to_path_buf(); | ||
let key_path = dir.join("cert.key.pem").to_path_buf(); | ||
|
||
let mut cert_modified = std::fs::metadata(&cert_path).unwrap().modified().unwrap(); | ||
let mut key_modified = std::fs::metadata(&key_path).unwrap().modified().unwrap(); | ||
let client_cert = std::fs::read_to_string(&cert_path).unwrap(); | ||
let client_key = std::fs::read_to_string(&key_path).unwrap(); | ||
|
||
let dynamic_certs = | ||
TlsDynamicCerts::new(TlsCerts::default().with_pem_identity(&client_cert, &client_key).unwrap()); | ||
let dynamic_certs_updator = dynamic_certs.clone(); | ||
|
||
let (feedback_sender, feedback_receiver) = mpsc::unbounded(); | ||
let task = asyncs::spawn(async move { | ||
let (tx, mut rx) = mpsc::unbounded(); | ||
let mut watcher = notify::recommended_watcher(EventSender { sender: tx }).unwrap(); | ||
watcher.watch(&cert_path, RecursiveMode::NonRecursive).unwrap(); | ||
watcher.watch(&key_path, RecursiveMode::NonRecursive).unwrap(); | ||
while rx.next().await.is_some() { | ||
let updated_cert_modified = std::fs::metadata(&cert_path).unwrap().modified().unwrap(); | ||
let updated_key_modified = std::fs::metadata(&key_path).unwrap().modified().unwrap(); | ||
if updated_cert_modified <= cert_modified || updated_key_modified <= key_modified { | ||
continue; | ||
} | ||
cert_modified = updated_cert_modified; | ||
key_modified = updated_key_modified; | ||
let client_cert = std::fs::read_to_string(&cert_path).unwrap(); | ||
let client_key = std::fs::read_to_string(&key_path).unwrap(); | ||
dynamic_certs_updator | ||
.update(TlsCerts::default().with_pem_identity(&client_cert, &client_key).unwrap()); | ||
feedback_sender.unbounded_send(()).unwrap(); | ||
} | ||
}) | ||
.attach(); | ||
(dynamic_certs, feedback_receiver, task) | ||
} | ||
|
||
fn generate_cert(ca: &Ca, dir: &Path, name: &str) { | ||
let client_cert = generate_client_cert(name, &ca.issuer); | ||
let file = AtomicWriteFile::open(dir.join("cert.pem")).unwrap(); | ||
write!(&file, "{}", client_cert.cert.pem()).unwrap(); | ||
file.commit().unwrap(); | ||
|
||
let file = AtomicWriteFile::open(dir.join("cert.key.pem")).unwrap(); | ||
write!(&file, "{}", client_cert.signing_key.serialize_pem()).unwrap(); | ||
file.commit().unwrap(); | ||
} | ||
|
||
pub async fn regenerate_cert(&mut self, client_name: &str) { | ||
Self::generate_cert(&self.ca, self.dir.path(), client_name); | ||
self.feedback.next().await; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you think it's worth having a FileBased implementation into the main package so that it's easily reusable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think such an official file based implementation may have biased on the change detection. And, worse, it will propgate this to applications. I think we should avoid such a thing in library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds good to me! The example looks clean enough to use as a basis for an implementation anyway. Thanks
src/tls/options.rs
Outdated
/// Cell to keep up to date [TlsCerts]. | ||
#[derive(Clone, Debug)] | ||
pub struct TlsDynamicCerts { | ||
certs: Arc<RwLock<(u64, Arc<TlsCerts>)>>, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to use this from the branch and it seems it's not visible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My fault! I have referenced it in integration test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I was able to create and test a simple file-based reloader that can be plugged into the new structs. It works great
e3adbc4
to
c70064a
Compare
This allows creating a client with dynamic tls certificates. When created this way, on reconnection the client will use latest tls certificates. This allows us to reload refreshed certificates stored somewhere in client side. This commit also adds support for crls in cert verifier to reject revoked server certs. Resolves #59.
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #66 +/- ##
==========================================
+ Coverage 85.53% 85.83% +0.30%
==========================================
Files 37 39 +2
Lines 5323 5782 +459
==========================================
+ Hits 4553 4963 +410
- Misses 770 819 +49 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
This allows creating a client with dynamic tls certificates. When created this way, on reconnection the client will use latest tls certificates.
This allows us to reload refreshed certificates stored somewhere in client side.
This commit also adds support for crls in cert verifier to reject revoked server certs.
Resolves #59.